﻿using System;
using System.Collections.Generic;

namespace HaskellPlugin {
	class Lexer {
		/// <summary>
		/// List of the all literal keywords.
		/// /// </summary>
		static List<string> keywords = new List<string> { 
				"as", "case", "of", 
				"class", "data", "family", 
				"instance", "default", "deriving",
		    "do", "forall", "foreign",
		    "hiding", "if", "then", "else",
		    "import", "infix", "infixl", "infixr",
				"let", "in", "mdo", "module",
				"newtype", "proc", "qualified", 
				"rec", "type", "where" };

		/// <summary>
		/// Finds the index of end of lexeme with condition predicate.
		/// </summary>
		/// <param name="predicate">defines the condition of termination</param>
		/// <param name="text">the string which will observed</param>
		/// <param name="cursor">the starting index</param>
		/// <returns>the index of the end</returns>
		static int FindLexemeEnd(Func<char, bool> predicate, string text, int cursor) {
			while (predicate(text[cursor++]));
			return cursor - 1;
		}

		/// <summary>
		/// Finds the index of end of one-line comment.
		/// </summary>
		/// <param name="text">the string which will observed</param>
		/// <param name="cursor">the starting index</param>
		/// <returns>the index of the end</returns>
		static int EndOfOneLineComment(string text, int cursor) {
			return text.IndexOf("\n", cursor);
		}

		/// <summary>
		/// Finds the index of end of multi-line comment.
		/// </summary>
		/// <param name="text">the string which will observed</param>
		/// <param name="cursor">the starting index</param>
		/// <returns>the index of the end</returns>
	  static int EndOfMultiLineComment(string text, int cursor) {
			return text.IndexOf("-}", cursor);
		}


		static Tuple<int, Token> ExtractOneLineComment(string text, int cursor) {
		  var end     = EndOfOneLineComment(text, cursor);
			var comment = text.Substring(cursor, end - cursor);
			var token   = new Token(comment, Token.Kind.LineComment);
			return new Tuple<int, Token>(end, token);
		}

		
		static Tuple<int, Token> ExtractTextToken(string text, int cursor) {
			var end = FindLexemeEnd(x => char.IsLetterOrDigit(x) || x == ("'"[0]), text, cursor);
			var lexemeID = text.Substring(cursor, end - cursor);

			Token token = (char.IsUpper(lexemeID[0])) ?
				new Token(lexemeID, Token.Kind.TypeID) : (
					(keywords.Contains(lexemeID)) ?
						new Token(lexemeID, Token.Kind.Keyword) :
						new Token(lexemeID, Token.Kind.ID));

			return new Tuple<int, Token>(end, token);
	  }


		static IEnumerable<Tuple<int, Token>> 
				ExtractMultiLineComment(string text, int cursor) {
			var endOfOLC = EndOfOneLineComment(text, cursor);
			var endOfMLC = EndOfMultiLineComment(text, cursor);

			while (endOfOLC < endOfMLC) {
				var comment = text.Substring(cursor, endOfOLC - cursor);
				
				yield return new Tuple<int, Token>(cursor, 
						new Token(comment, Token.Kind.LineComment));
				//yield return new Tuple<int, Token>(cursor, 
				//		new Token(comment, Token.Kind.NewLine));

				cursor = endOfOLC + 1;

				endOfOLC = EndOfOneLineComment(text, cursor);
				endOfMLC = EndOfMultiLineComment(text, cursor);
			}

			endOfMLC += 2;
			var iEndComment = endOfMLC - cursor;
			
			if (iEndComment >= 0) {
				var restComment = text.Substring(cursor, iEndComment);
				cursor = endOfMLC;
				yield return new Tuple<int, Token>(cursor,
						new Token(restComment, Token.Kind.LineComment));
			}
			else {
				cursor = text.Length;
				yield return new Tuple<int, Token>(cursor,
						new Token("", Token.Kind.Undefined)); 
			}
		}

		static Tuple<int, Token> ExtractConstStringLiteral(string text, int cursor) {
			var end   = text.IndexOf('"', cursor + 1) + 1;
			var token = new Token("", Token.Kind.Undefined);

			if (end > cursor) {
				string strConst = text.Substring(cursor, end - cursor);
				token  = new Token(strConst, Token.Kind.StringConst);
				cursor = end;
			}	else {
				cursor = text.Length;
			}

			return new Tuple<int, Token>(cursor, token);
		}		

		/// <summary>
		/// Generator which produces the tokens from a raw string text.
		/// </summary>
		/// <param name="text">Source code string</param>
		/// <returns>The lazy list of tokens</returns>
		public static IEnumerable<Token> Tokens(string text) {
			var len = text.Length;

			for (var i = 0; i < len; ) {
				var s = text[i];

				//  text lexeme
				if (char.IsLetter(s)) {
					var tpl = ExtractTextToken(text, i);
					yield return tpl.Item2;
					i = tpl.Item1;				
				}
				// new line
				else if (s == '\n') {
					yield return new Token("", Token.Kind.NewLine);
					++i;
				}
			  // space
				else if (s == ' ') {
					yield return new Token(" ", Token.Kind.Space);
					++i;
				}
				//  one-line comment
				else if (s == '-' && text[i + 1] == '-') {
					var tpl = ExtractOneLineComment(text, i);
					yield return tpl.Item2;
					i = tpl.Item1 + 1;
				}
			  // multi line comment
				else if (s == '{' && text[i + 1] == '-') {
					var tokensAndEnds = ExtractMultiLineComment(text, i);
					foreach(var tokenAndEnd in tokensAndEnds) {
						var end   = tokenAndEnd.Item1;
						var token = tokenAndEnd.Item2;
						i = end;
						yield return token;
					}
				} 
				// const string literal
				else if (s == '"') {
					var tpl = ExtractConstStringLiteral(text, i);
					var end = tpl.Item1;
					var token = tpl.Item2;
					yield return token;
					i = end;
				}
				else if (s == '\r') ++i;
				// undefined 
				else {
					yield return new Token(char.ToString(s), Token.Kind.Undefined);
					++i;
				}
			}
		}
	}
}